package socket;

import com.sun.xml.internal.ws.resources.SenderMessages;
import io.BRDemo;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
* 聊天室的服务端
* */
public class Server {
    /**
     * java.net.ServerSocket
     * 运行在服务端的，主要作用
     * 1:用于向系统申请服务端口，以便客户端可以通过该端口与服务端建立连接
     * 2:监听服务端口，一旦一个客户端连接，则会创建一个Socket，使用该Socket就可以
     *   和建立连接的客户端交互了
     *
     *  如果Socket比喻为一个插座，那么ServerSocket相当于“客服”这边的总机，上面
     *  有一堆插座。
     */
    private ServerSocket serverSocket;
    //为该集合存放所有客户端的输出流
   // private List<PrintWriter> allOut = new ArrayList<>();
    //第四天：key是客户端的昵称 value为对应的输出流
    private Map<String,PrintWriter> allOut =new HashMap<>();

    //5:线程池：
    private ExecutorService threadPool;

    public Server(){
        try {
            System.out.println("正在启动服务端......");
            /*
                实例化ServerSocket时要指定服务端口，客户端就是通过这个端口
                与服务端建立连接的。
                注意:该端口不能和当前操作系统下的其他应用程序冲突，否则会报错:
                java.net.BindException: Address already in use: JVM_Bind
                如果出现上述异常，解决办法:更换端口号
             */
            serverSocket = new ServerSocket(8088);
            //5
            threadPool = Executors.newFixedThreadPool(40);

            System.out.println("服务端启动完毕！");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            /*
                ServerSocket提供的重要方法:
                Socket accept()
                该方法是一个阻塞方法，调用会"卡住"并开始等待客户端的连接，一旦
                一个客户端进行连接，此时accept方法会立即返回一个Socket实例，
                通过这个Socket就可以和连接的客户端交互了。相当于"接电话"操作
             */
            while (true) {
                System.out.println("等待客户端连接........");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了！");
                //启动一个线程来处理与客户端的交互
                ClientHandler handler = new ClientHandler(socket);
//5:                Thread t = new Thread(handler);
//                t.start();
                threadPool.execute(handler);


            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
    private class ClientHandler implements Runnable{
        private Socket socket;
        private  String host;//记录该客户端的ip地址
        private String nickname;//记录该客户端的昵称

        public ClientHandler(Socket socket){
            this.socket = socket;
            //通过socket获取远端（客户端）的ip地址
            host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            PrintWriter pw = null;
            try {
                 /*
                Socket提供的方法:
                InputStream getInputStream()
                通过该输入流可以读取与当前Socket建立连接的对方计算机发送过来的字节
             */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);//转换流
                BufferedReader br = new BufferedReader(isr);
                //首先单独读取客户端发来的第一行字符串，它应当是客户端的昵称
                nickname = br.readLine();
                //通过socket获取输出流，用于消息发送给该客户端（第三天）
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);

                //第四：同步监视器对象可以指定为临界资源
                 pw = new PrintWriter(bw, true);
                 /*
                    通常同步监视器对象可以指定为临界资源
                    原则:抢谁就锁谁
                 */
                 synchronized (allOut) {
                     //将该输出流存入到共享集合allOut中
                     // allOut.add(pw);添加昵称
                     //allOut.put(nickname,pw);
                     //解决重名问题
                     int num=1;
                     String newNickname = nickname;//昵称
                     while (true){
                         if (allOut.containsKey(newNickname)){
                             newNickname = nickname + num;
                             num++;
                         }else {
                             allOut.put(newNickname,pw);
                             nickname=newNickname;
                             break;
                         }
                     }

                 }

                //第四天：广播消息，告知所有客户端该用户上线了
                sendMessage(nickname + "上线了，当前在线人数" + allOut.size());
                String message;
                /*
                readLine用于读取来自客户端发送过来的一行字符串。
                会有几种情况:
                1:当客户端没有发送数据时，此时readLine会阻塞
                2:当客户端发送一行字符串过来时，此时readLine方法会立即返回这行内容
                3:当客户端断开连接
             */
                while ((message = br.readLine()) != null) {
                    if (message.startsWith("@")){
                        sendMessageToSomeOne(message);
                    }else {
                        //输出格式：昵称【ip】说：xxxxxx
                        sendMessage(nickname + "[" + host + "]说：" + message);

                        //5将消息保存到数据库中：
                        saveMessage(nickname,"null",message);

                    }
                    //将消息发送给客户端
                    //第四天：
                    /* System.out.println(nickname + "[" + host + "]说：" + message);
                    for (PrintWriter o : allOut) {
                        pw.println(nickname + "[" + host + "]说：" + message);
                    }*/

                }
            } catch (IOException e) {
                e.printStackTrace();
            }//第四天：
            finally {
                //处理客户端断开连接的情况
                //1将该客户端的输出流从共享集合中移除
                synchronized (allOut) {
                    //allOut.remove(pw);改变为map
                    allOut.remove(nickname);
                }
                //2通知所有用户该客户端下线了
                sendMessage(nickname+"下线了，当前在线人数："+allOut.size());
                //3关闭socket释放资源
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        /**
         * 将给定消息发送给所有客户端
         * @param message
         */
            public void sendMessage(String message){
                System.out.println(message);
                synchronized (allOut) {
//                    for (PrintWriter o : allOut) {
//                        o.println(message);
//                    }
                    for (PrintWriter o : allOut.values()) {
                        o.println(message);
                    }
                }
            }
//第四;私聊
        private void sendMessageToSomeOne(String message){
             /*
                私聊格式:
                @昵称:消息
                1:获取对方昵称
                2:根据昵称去allOut中获取对方的输出流
                3:将消息单独发送给对方
             */
            /*
                必要的格式验证
                @.+:.+
             */
            if (message.matches("@.+:.+")){//判断是否符合格式
                String toNickName = message.substring(1,message.indexOf(":"));//获取对方昵称
                if (allOut.containsKey(toNickName)){//对方存在
                    PrintWriter pw = allOut.get(toNickName);

                    //5:
                    message = message.substring(message.indexOf(":")+1);//5
//5                   String toMessage = nickname+"悄悄对你说："+message.substring(message.indexOf(":")+1);
                    String toMessage = nickname+"悄悄对你说："+message;//5
                    pw.println(toMessage);

                    //5 将消息保存到数据库中
                    saveMessage(nickname,toNickName,message);

                }else {//对方不存在
                    //用当前用户的昵称获取自己的输出流，通知他没有该用户
                    PrintWriter pw = allOut.get(nickname);
                    pw.println(toNickName+"不在线");
                }
            }else {
                PrintWriter pw = allOut.get(nickname);
                pw.println("没有该用户");
            }

        }


        //第5天: 将聊天信息保存到数据库中
        /**
         * 将聊天信息保存到数据库中
         * @param fromUser 谁发的
         * @param toUser  发给谁
         * @param message 私聊消息
         * */

        private void saveMessage(String fromUser,String toUser,String message){
            /*
            * CREATE TABLE  chatinfo(
                  id INT PRIMARY KEY AUTO_INCREMENT,
                  content VARCHAR(255),
                  from_user VARCHAR(30),
                  to_user VARCHAR(30)
               );
            * */

            //将聊天信息插入到上个表中
            try (
                    Connection connection = DBUtil.getConnection();
            ){
                String sql = "INSERT INTO chatinfo(content,from_user,to_user) "+
                              "VALUES(?,?,?)";
                PreparedStatement ps = connection.prepareStatement(sql);
                ps.setString(1,message);
                ps.setString(2,fromUser);
                ps.setString(3,toUser);
                ps.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}




